#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <algorithm>
#include <vector>
#include <cmath>

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/soundcard.h>

#include "welltempMore.h"
#include "key2wheel.h"
using namespace std;

vector<double> v ( wheelNum + 1, 0.1 );
double drawbarLevel [9] = { 0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0};
vector<int> drawbar ( 9, 0 );
vector<int> ppos ( wheelNum + 1, 0 );

const double scale = ( 1.0 / 512 );

const int LENGTH = 3;
const int RATE = sampleRate;
const int SIZE = 16;
const int CHANNELS = 1;

int fd;	/* sound device file descriptor */

short *buf = NULL;

void InitDevDsp()
{
    buf = new short[LENGTH*RATE];
    int arg;	/* argument for ioctl calls */
    int status;   /* return status of system calls */

    /* open sound device */
    fd = open ( "/dev/dsp", O_RDWR );
    if ( fd < 0 )
    {
        perror ( "open of /dev/dsp failed" );
        exit ( 1 );
    }

    /* set sampling parameters */
    arg = SIZE;	   /* sample size */
    status = ioctl ( fd, SOUND_PCM_WRITE_BITS, &arg );
    if ( status == -1 )
        perror ( "SOUND_PCM_WRITE_BITS ioctl failed" );
    if ( arg != SIZE )
        perror ( "unable to set sample size" );

    arg = CHANNELS;  /* mono or stereo */
    status = ioctl ( fd, SOUND_PCM_WRITE_CHANNELS, &arg );
    if ( status == -1 )
        perror ( "SOUND_PCM_WRITE_CHANNELS ioctl failed" );
    if ( arg != CHANNELS )
        perror ( "unable to set number of channels" );

    arg = RATE;	   /* sampling rate */
    status = ioctl ( fd, SOUND_PCM_WRITE_RATE, &arg );
    if ( status == -1 )
        perror ( "SOUND_PCM_WRITE_WRITE ioctl failed" );
}

void EndDevDsp()
{
    if( buf != NULL )
        delete []buf;
    close ( fd );
}
void PullDrawbar ( const string & newLevel ) throw ( string )
{
    if ( newLevel.size() != 9 || newLevel.find_first_not_of ( "012345678" ) != string::npos )
    {
        throw string ( "WrongDrawbarValue" );
    }
    for ( int i = 0;i < 9;++i )
    {
        drawbar[i] = newLevel[i] - '0';
    }
}

void SetV ( int wheelNo, double value ) throw ( string )
{
    if ( wheelNo > wheelNum || wheelNo <= 0 )
    {
        throw string ( "WrongWheelNo" );
    }
    v[wheelNo] = value;
}

void PrintV()
{
    for ( int i = 1; i <= wheelNum; ++i )
    {
        cout << "wheel" << setw ( 2 ) << i << " : " << v[i] << endl;
    }
}

inline void PposInc()
{
    for ( int i = 1;i <= wheelNum; ++i )
    {
        ppos[i] = ( ppos[i] + 1 ) % lens[i];
    }
}

void Play ( int key ) throw ( string )
{
    if ( key > 61 || key < 1 )
        throw string ( "WrongKey" );
    for ( int i = 0; i < RATE*LENGTH; ++i )
    {
        buf[i] = 0X0000;
        double temp = buf[i];
        for ( int j = 0;j < 9;++j )
        {
            int theWheel = key2wheel[key][j];
            temp += wheels[ theWheel ][ppos[theWheel]] * drawbarLevel[ drawbar[j] ] * v[theWheel] * scale;
        }
        buf[i] = temp;
        PposInc();
    }

    write ( fd, buf, RATE*LENGTH*2 );
}

void WriteWav(const string& name="")
{
    ofstream file(name.c_str());
    if (!file)
        throw string("Cannot open file: ")+name;

    long buflen = LENGTH*RATE*2;
    long totalpacklen = 36 + buflen;
    long fmtchunklen = 0x10;
    short zeroone = 0x01;
    short mono =0x01;
    long s_rate = long(RATE);
    long bps = s_rate;
    short bpsample = 2;
    short bitpsample = 16;

    file <<"RIFF";
    file.write( (char *) &totalpacklen, 4);
    file << "WAVEfmt ";
    file.write( (char *) &fmtchunklen, 4);
    file.write( (char *) &zeroone, 2);
    file.write( (char *) &mono, 2);
    file.write( (char *) &s_rate, 4);
    file.write( (char *) &bps, 4);
    file.write( (char *) &bpsample, 2);
    file.write( (char *) &bitpsample, 2);
    file<<"data";
    file.write( (char *) &buflen, 4);
    file.write( (char *) buf, buflen);

    file.close();
}
void OnHelp()
{
    cout<< "Commands:"<<endl;
    cout << "D to adjust drawbar, 9 digits, e.g. d 888432100"<<endl;
    cout << "N to play a note, e.g. n 37"<<endl;
    cout << "R to repeat last note"<<endl;

    cout << "H for help"<<endl;
    cout << "Q to quit"<<endl;
}

int main ( int argc, char *argv[] )
{
    InitDevDsp();
    char comType;
    bool on = true;
    while ( on )
    {
        for (int i= 0; i<9;++i)
            cout<<drawbar[i];
        cout <<" $ ";
        cin >> comType;
        string strval;
        int intval;
        try
        {
            switch ( comType )
            {
            case 'd':
            case 'D':
                cin >> strval;
                PullDrawbar ( strval );
                break;
            case 'n':
            case 'N':
                cin >> intval;
                Play ( intval );
                break;
            case 'q':
            case 'Q':
                on = false;
                break;
            case 'r':
            case 'R':
                Play(intval);
                break;
            case 'w':
            case 'W':
                cin >> strval;
                WriteWav(strval);
                break;
            case 'h':
            case 'H':
                OnHelp();
                break;
            default:
                cout << "Command unknown, try again" << endl;
                cin.ignore ( 1024, '\n' );
            }
        }
        catch ( string s )
        {
            cout << s << endl;
        }
    }



    EndDevDsp();
    return 0;
}

